﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#if DESIGNER
using System.Drawing;
#else
using Microsoft.Xna.Framework;
#endif
using System.IO;

namespace RiverRaidDesigner
{
    public class ItemWithPositionAndDirection
    {
        public Items itemType;
        public Point position;
        public bool flipDirection;

        public ItemWithPositionAndDirection(Items i_type, Point i_pos, bool i_flip)
        {
            itemType = i_type;
            position = i_pos;
            flipDirection = i_flip;
        }
    }

    public enum Items
    {
        Fuel,
        Ship,
        Heli,
        House,
        Tree,
        Bridge,
        Jet
    }

    public class RandomLevelGenerator
    {
        Random r;
        int screenWidth = 1280; //pixels
        //int levelHeight = 4500; //pixels
        int blockHeight = 200; //250; //pixels
        int blockCount = 18; //change this to minBlockCount and maxBlockCount, then generate a random number.
        int minPassableAreaWidth = 150; //pixels
        int linesInBlock = 250 / 10; //determines block height
        int lineWidth = 30;

        int maxIslandWidth = 0;
        
        int maxIslandDelta = 100;
        int minIslandDelta = 0;

        int maxOuterDelta = 200;
        int minOuterDelta = 0;

        int minOuterX = 400;

        float islandWrapChance = 0.4f;
        float houseChance = 0.4f;

        float fuelChance =  0.3f;
        float jetChance = 0.1f;
        float emptyChance = 0.2f;
        int randomMax = 100;

#if !DESIGNER
        struct Image
        {
            public int Width;
            public int Height;
            public static Image FromFile(string file)
            {
                if (file.Contains("fuel")) return new Image { Width = 92, Height = 208 };
                else if (file.Contains("ship")) return new Image { Width = 200, Height = 71 };
                else if (file.Contains("heli")) return new Image { Width = 113, Height = 64 };
                else if (file.Contains("house")) return new Image { Width = 185, Height = 68 };
                else if (file.Contains("tree")) return new Image { Width = 98, Height = 69 };
                else if (file.Contains("bridge")) return new Image { Width = 341, Height = 187 };
                else if (file.Contains("jet")) return new Image { Width = 72, Height = 40 };
                else return new Image();
            }
        }
#endif

        Image fuelImage, shipImage, heliImage, houseImage, treeImage, bridgeImage, jetImage,
            roadImage, raiderImage;

        List<Point> outerWalls;
        List<Point> innerWalls;
        List<ItemWithPositionAndDirection> items;

        public RandomLevelGenerator()
        {
            r = new Random(1991); //Generate random numbers using a constant seed
            //r = new Random();
        }

        public void Reseed(int seed)
        {
            r = new Random(1991 + seed);
        }

        public void Generate(string filename = "test.rrx")
        {
            int bridgeW, bridgeX;

            fuelImage = Image.FromFile(@"RiverRaid\fuel.png");
            shipImage = Image.FromFile(@"RiverRaid\ship.png");
            heliImage = Image.FromFile(@"RiverRaid\heli.png");
            houseImage = Image.FromFile(@"RiverRaid\house.png");
            treeImage = Image.FromFile(@"RiverRaid\tree.png");
            bridgeImage = Image.FromFile(@"RiverRaid\bridge.png");
            jetImage = Image.FromFile(@"RiverRaid\jet.png");
            roadImage = Image.FromFile(@"RiverRaid\road.png");
            raiderImage = Image.FromFile(@"RiverRaid\raider.png");
            float imageScale = 0.4f;

            bridgeW = (int)(bridgeImage.Width * imageScale);
            bridgeX = (int)(screenWidth / 2 - (bridgeImage.Width * imageScale) / 2);

            outerWalls = new List<Point>();
            innerWalls = new List<Point>();
            items = new List<ItemWithPositionAndDirection>();

            //Build the beginning of the section
            outerWalls.Add(new Point((int)(bridgeW / 2), 0));
            outerWalls.Add(new Point((int)(bridgeW / 2), -blockHeight));

            int lineHeight = blockHeight / linesInBlock;
            int currentY = -blockHeight;
            int lastO = bridgeW / 2;
            int maxO = screenWidth / 2 - 100;
            int minO = minPassableAreaWidth / 2;
            int lastI = 0;

            int maxBlockWidth = linesInBlock * lineWidth;

            //Build section body
            for (int i = 0; i < blockCount; i++)
            {
                //First, decide a number
                int islandDelta = r.Next(minIslandDelta, maxIslandDelta);
                int outerDelta = r.Next(minOuterDelta, maxOuterDelta);
                int sectionY = currentY;
                int sectionOX = lastO;
                int sectionIX = lastI;

                //Check if island's slope is less than exterior's
                islandDelta = Math.Min(islandDelta, (int)(outerDelta * 3 / 4));

                //Decide the direction
                int islandSign = 1;
                int outerSign = 1;

                //Too soon to make an island?
                if (i < 3)
                {
                    islandDelta = 0; //Need at least 3 blocks before we can make islands
                    islandSign = 0;
                }

                //Too late to continue making an island?
                else if (i > blockCount - 4 || (r.Next(randomMax) < islandWrapChance * randomMax && lastI > 0))
                {
                    islandDelta = lastI; //begin wrapping islands when it's only 3 blocks left in the section
                    islandSign = -1;
                }

                //Check if we can do the delta within a block
                islandDelta = Math.Min(maxBlockWidth, islandDelta);
                outerDelta = Math.Min(maxBlockWidth, outerDelta);

                //Round it to line width
                islandDelta = (int)(islandDelta / lineWidth) * lineWidth;
                outerDelta = (int)(outerDelta / lineWidth) * lineWidth;

                int safeAreaY = currentY;

                if (lastO + outerDelta > maxO) outerSign = -2;
                if (lastO - outerDelta < minO || (lastO - outerDelta < minOuterX && i > 2 && i < blockCount - 4))
                {
                    if (outerSign < 0) outerSign = 0;
                    else outerSign = 2;
                }
                if (outerSign == 1) outerSign *= r.Next(-randomMax/2, randomMax);
                int outerTarget = lastO + Math.Sign(outerSign) * outerDelta;

                if (islandSign > 0) //If we are allowed to decide about the size of the island
                {
                    if (outerTarget - (lastI + islandDelta) < minPassableAreaWidth)
                        islandSign = -2;
                    if (lastI - islandDelta < 0)
                    {
                        if (islandSign < 0) islandSign = 0;
                        else islandSign = 2;
                    }
                    else if (islandSign >= 0)
                    {

                    }
                    islandSign = Math.Sign(islandSign);
                }

                if (outerSign != 0)
                {
                    outerSign = Math.Sign(outerSign);
                    
                    //build the exterior
                    int steps = outerDelta / lineWidth;
                    for (int step = 0; step < steps; step++)
                    {
                        currentY -= lineHeight;
                        if (lastO + outerSign * lineWidth - lastI >= minPassableAreaWidth || i<=3)
                            lastO += outerSign * lineWidth;
                        outerWalls.Add(new Point(lastO, currentY));
                        safeAreaY = Math.Min(safeAreaY, currentY);
                    }
                    int stepsLeft = linesInBlock - steps;
                    if (stepsLeft > 0)
                    {
                        currentY -= stepsLeft * lineHeight;
                        outerWalls.Add(new Point(lastO, currentY));
                    }
                }
                else
                {
                    outerWalls.Add(new Point(lastO, currentY -= blockHeight));
                }

                int maxIsland = Math.Min(lastO, sectionOX) - minPassableAreaWidth;
                if (islandSign != 0)
                {
                    currentY = sectionY;

                    int steps = islandDelta / lineWidth;
                    for (int step = 0; step < steps; step++)
                    {
                        currentY -= lineHeight;
                        if (lastI + islandSign * lineWidth < maxIsland)
                            lastI += islandSign * lineWidth;
                        innerWalls.Add(new Point(lastI, currentY));
                        safeAreaY = Math.Min(safeAreaY, currentY);
                    }
                    int stepsLeft = linesInBlock - steps;
                    if (stepsLeft > 0)
                    {
                        currentY -= stepsLeft * lineHeight;
                        innerWalls.Add(new Point(lastI, currentY));
                    }
                }
                else
                {
                    if (lastI != 0)
                        innerWalls.Add(new Point(lastI, currentY));
                }

                //Create Items
                int itemRoullette = r.Next(randomMax);
                var jetCut = jetChance * randomMax;
                var fuelCut = fuelChance * randomMax;
                var houseCut = houseChance * randomMax;
                var emptyCut = emptyChance * randomMax;
                Items e = Items.House;
                if (Math.Abs(safeAreaY - currentY) < minPassableAreaWidth) ; //House it is.
                else 
                {
                    if (itemRoullette > emptyCut && itemRoullette <= jetCut + emptyCut)
                        e = Items.Jet;
                    else if (itemRoullette > jetCut + emptyCut && itemRoullette <= jetCut + emptyCut + fuelCut)
                        e = Items.Fuel;
                    else if (itemRoullette > jetCut + emptyCut + fuelCut)
                        e = (r.Next(randomMax) % 2 == 0 ? Items.Heli : Items.Ship);

                    if (e == Items.House) ;
                    else if (e == Items.Jet)
                    {
                        ItemWithPositionAndDirection ipd = new ItemWithPositionAndDirection(e,
                            new Point(r.Next(-screenWidth / 2, screenWidth / 2),
                                r.Next(currentY, (int)(safeAreaY - jetImage.Height * imageScale))),
                                r.Next(randomMax) % 2==0);
                        items.Add(ipd);
                    }
                    else
                    {
                        Image img = fuelImage;
                        if (e == Items.Heli) img = heliImage;
                        else if (e == Items.Ship) img = shipImage;

                        int itemHeight = (int)(imageScale * img.Height);
                        int itemWidth = (int)(imageScale * img.Width);

                        bool left = r.Next(randomMax) % 2 == 0;
                        int x = -r.Next(lastI, lastO - itemWidth - 10);
                        if (left)
                            x = r.Next(lastI + itemWidth + 10, lastO);
                        /*
                        int xMax = lastO - (minPassableAreaWidth - itemWidth) / 2;
                        int xMin = lastI + (minPassableAreaWidth - itemWidth) / 2;
                        if (xMax < xMin)
                        {
                            xMin *= -1;
                            xMax *= -1;
                        }
                        //if (xMin < xMax)
                        {
                            int x = r.Next(xMin, xMax);*/
                            items.Add(new ItemWithPositionAndDirection(e, new Point(x, currentY), (r.Next(randomMax) % 2 == 0) && (e != Items.Fuel)));
                        //}
                    }
                }
                //else if (e == Items.House)
                if (r.Next(randomMax) <= houseCut)
                {
                    int houseWidth = (int)(houseImage.Width * imageScale);
                    bool onIsland = lastI > houseWidth;
                    onIsland = onIsland && (r.Next(randomMax) % 2 == 0);
                    if (onIsland)
                        items.Add(new ItemWithPositionAndDirection(Items.House,
                            new Point(houseWidth / 2 + r.Next(5, lineWidth * 2 - 5), currentY + 5), false));
                    else
                    {
                        bool left = r.Next(randomMax) % 2 == 0;
                        if (left)
                            items.Add(new ItemWithPositionAndDirection(Items.House,
                                //new Point(r.Next(-lastO-houseWidth/*maxO*/-maxOuterDelta, -lastO - houseWidth), currentY + 5), false));
                                new Point(-lastO - houseWidth  / 2, currentY + 5), false));
                        else
                            items.Add(new ItemWithPositionAndDirection(Items.House,
                                //new Point(r.Next(lastO, lastO-houseWidth-maxOuterDelta), currentY + 5), false));
                                new Point(lastO + houseWidth * 4 / 3, currentY + 5), false));
                    }
                }
                
            } //end for

            //build the ending of the section
            {
                int wrapOuterTarget = bridgeW / 2;
                int outerDelta = wrapOuterTarget - lastO;
                int direction = Math.Sign(outerDelta);
                int steps = Math.Abs(outerDelta / lineWidth);
                for (int step = 0; step < steps; step++)
                {
                    currentY -= lineHeight;
                    lastO += direction * lineWidth;
                    outerWalls.Add(new Point(lastO, currentY));
                }
                currentY -= blockHeight;
                outerWalls.Add(new Point(lastO, currentY));
                items.Add(new ItemWithPositionAndDirection(Items.Bridge, new Point(lastO, (int)(currentY + bridgeImage.Height * imageScale)), false));
            }

            //SAVE FILE
            StreamWriter sw = new StreamWriter(filename);
            sw.WriteLine("~2");
            sw.WriteLine("~inner");
            foreach (var item in innerWalls)
                sw.WriteLine(item.X.ToString() + "," + item.Y.ToString());
            sw.WriteLine("~outer");
            foreach (var item in outerWalls)
                sw.WriteLine(item.X.ToString() + "," + item.Y.ToString());
            sw.WriteLine("~entities");
            foreach (var item in items)
            {
                sw.WriteLine(item.position.X.ToString() + "," + item.position.Y.ToString());
                sw.WriteLine(item.flipDirection.ToString());
                sw.WriteLine((char)(item.itemType));
            }
            sw.Close();

        } //end method
    } //end class
} //end namespace
